#include "TutorApp6.h"
#include <BlobzUtility.h>

#ifdef WIN32
#ifdef Tutorial6_EXPORTS
#define TUTORAPP6_API __declspec(dllexport)
#else
#define TUTORAPP6_API __declspec(dllimport)
#endif
#else
#define TUTORAPP6_API
#endif

extern "C" TUTORAPP6_API Blobz::BaseApplication* GetInstance()
{
	Tutorial6::TutorApp6* pTutorial = new Tutorial6::TutorApp6();
	return (Blobz::BaseApplication*)pTutorial;
}

extern "C" TUTORAPP6_API unsigned int GetMajorReleaseVersion()
{
	return NUI_RELEASE_MAJOR_VER;
}

extern "C" TUTORAPP6_API unsigned int GetMinorReleaseVersion()
{
	return NUI_RELEASE_MINOR_VER;
}

extern "C" TUTORAPP6_API const char* GetAppName()
{
	return "Tutorial6";
}

namespace Tutorial6
{
	
	/**
	* Constructor
	*/
	TutorApp6::TutorApp6() : Interactable(),
		mpModel(NULL),
		mShaderManager(),
		mpRootNode(NULL),
		mpMaterial(NULL),
		mpLightNode(NULL),
		mpLightNodeInverse(NULL),
		mpObjectNode(NULL),
		mDistance(20.f),
		mMaxDistance(30.f),
		mZoomSensitivity(1),
		mMinZoomDistance(10),
		mSpinSensitivity(1.5),
		mLastPos()
	{
		setScaleable(false);
		setScale(1);
		setPosition(QVector2D(0,0));
	}
	
	/**
	* Destructor
	*/
	TutorApp6::~TutorApp6()
	{
	}

	/**
	* Loads and setups up all the required objects and gui elements
	*/
	bool TutorApp6::onInitialize()
	{
		// Tell the application to use a fixed aspect ratio so that our view content is not stretched
		getWindow().setUseFixedRatio(true);

		// load the background texture
		mBackgroundTexture.load(getApplicationDirectory()+"bg.png", true);

		//set up the rootnode for the scenegraph to which the 3d object will be attached
		mpRootNode = new Blobz::Node("Root");

		//set up the lighting
		float light_Z_Distance = 1000.f;
		mpLightNode = new Blobz::LightNode("MainLight");
		mpLightNode->setAmbient(QVector4D(0.5f,0.5f,0.5f,1));
		mpLightNode->setDiffuse(QVector4D(0.8f,0.8f,0.8f,1));
		mpLightNode->setPosition(-5, 10, light_Z_Distance);		

		mpLightNodeInverse = new Blobz::Node("Down");
		mpLightNodeInverse->setPosition(5, -10, -light_Z_Distance);

		mpLightNode->addChildren(mpLightNodeInverse);
		mpRootNode->addChildren(mpLightNode);

		//set object node
		mpObjectNode = new Blobz::Node("ObjectNode");
		mpLightNodeInverse->addChildren(mpObjectNode);

		return true;
	}
	
	/**
	* On Reload Handler
	*/
	void TutorApp6::onReload()
	{
	}
	
	/**
	* Unload all loaded objects
	*/
	void TutorApp6::onDeinitialize()
	{
		// unload all objects
		if(mpModel != NULL)
		{
			mpModel->getRootNode()->getParent()->removeChildren();
			mpModel->unload();
			delete mpModel;
			mpModel = NULL;
		}		
		if(mpMaterial != NULL)
		{
			delete mpMaterial;
			mpMaterial = NULL;
		}
		mpRootNode->clearChildren();
		mShaderManager.deInitialize();
		mBackgroundTexture.unload();
	}
	
	/**
	* Update event handler
	*/
	void TutorApp6::onUpdate(float inFrameTime)
	{
		// update the shader camera -> mDistance determines the zoom value
		mShaderManager.setCameraPosition(QVector3D(0, 0, mDistance));
		Interactable::update(inFrameTime);

		// calculate the object rotation values based on location
		QVector2D deltaPos(0,0);
		if(Interactable::getNumFingers()<2)
		{
			deltaPos = -(QVector2D(Interactable::getPosition())-mLastPos);
			mLastPos = Interactable::getPosition();		
		}
		else
		{		
			mLastPos = Interactable::getPosition();
		}
		mpObjectNode->setRotationY(((Blobz::PI)/mMaxDistance*deltaPos.x()*mDistance)*mSpinSensitivity);
		mpObjectNode->setRotationX(((Blobz::PI)/mMaxDistance*(-deltaPos.y())*mDistance)*mSpinSensitivity);

		// send update events to scene graph
		mpRootNode->update(inFrameTime);
	}
	
	/**
	* Render event handler
	*/
	void TutorApp6::onDraw()
	{
		// tell Snowflake that we want to start rendering
		getWindow().enableRendering();

		// screen setup, part of the Snowflake framework
		getWindow().defaultRenderSetup(QVector4D(1, 1, 1, 1));

		// Render the background
		renderBackground();

		glColor4f(1,1,1,1);

		// check if the model is not loaded yet
		if(mpModel == NULL)
		{
			// Shader manager has to be initialized after opengl context is created so it is safest to initialize the shader manager in onDraw
			if(mShaderManager.initialize())
			{
				// Compile the vertex and fragment shaders
				CGprogram mFragmentProgram = mShaderManager.getFragmentShader("../data/Shaders/fPhong.cg");
				CGprogram mVertexProgram = mShaderManager.getVertexShader("../data/Shaders/vPhong.cg");

				//load the material type as determined by the shaders 
				mpMaterial = new Blobz::Material();
				mpMaterial->setShaderManager(&mShaderManager);
				mpMaterial->assignFragmentShader(mFragmentProgram);
				mpMaterial->assignVertexShader(mVertexProgram);
			}				

			// set the shader manager for collada renderer
			Blobz::ColladaScene::setShaderManager(&mShaderManager);

			// load the Collada scene and load it resources
			mpModel = new Blobz::ColladaScene(getApplicationDirectory()+"models/watchAp.dae");
			mpModel->loadResource();

			// wrap the collada scene within a Node class for easy manipulation
			mpModel->setParentNode(mpObjectNode->getName().toStdString());
			mpObjectNode->addChildren(mpModel->getRootNode());			
		}				

		// Setup opengl to render in 3d
		glClear(GL_DEPTH_BUFFER_BIT);	// Clear Depth Buffer
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glShadeModel(GL_SMOOTH);

		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		gluPerspective(75.0f, getWindow().getWindowAspect(), 0.1f, 1000.0f);

		glEnable(GL_BLEND);			
		glEnable(GL_DEPTH_TEST);

		// Start the shader engine
		mShaderManager.begin();

		// set a viewport to use shader camera for render
		gluLookAt(mShaderManager.getCameraPosition().x(), mShaderManager.getCameraPosition().y(), mShaderManager.getCameraPosition().z(), 0, 0, 0, 0, 1, 0);

		// render the scenegraph
		if(mpModel != NULL)
			mpRootNode->render();

		// stop the shader engine
		mShaderManager.end();

		glDisable(GL_DEPTH_TEST);

		// reset the system to render in 2d
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();

		glOrtho(0,1,0,1,0,1);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glScalef(1/getWindow().getWindowAspect(),1,1);

		getWindow().disableRendering();
	}

	/**
	* Triggered when there is a finger down event on the screen
	* \param inData holds the touch information
	*/
	void TutorApp6::onFingerDown(const TouchListener::TouchData& inData)
	{
		// send the touch event to the Finger Manager and the Interactable parent class
		Interactable::fingerDown(inData);
		mFingerManager.onFingerDown(inData);
	}

	/**
	* Triggered when there is a finger up event on the screen
	* \param inData holds the touch information
	*/
	void TutorApp6::onFingerUp(const TouchListener::TouchData& inData)
	{
		// send the touch event to the Finger Manager and the Interactable parent class
		Interactable::fingerUp(inData);
		mFingerManager.onFingerUp(inData);
	}

	/**
	* Triggered when there is a finger update event on the screen
	* \param inData holds the touch information
	*/
	void TutorApp6::onFingerUpdate(const TouchListener::TouchData& inData)
	{
		mFingerManager.onFingerUpdate(inData);
		if(Interactable::getNumFingers() < 2 )
		{
			Interactable::fingerUpdate(inData);			
		}
		else 
		{
			// calculate the angle and position delta from the multitouch gestures
			float deltaAngle = Blobz::degToRad(mFingerManager.angleChange());
			float deltaFingerPositions = mFingerManager.distanceChange();
			if(fabs(deltaFingerPositions) > 0.002f)
			{
				// apply the zoom gestures delta to zoom the object
				mDistance -= deltaFingerPositions*mMaxDistance; 
				mLastPos = Interactable::getPosition();
			}
			else
			{			
				Blobz::Interactable::fingerUpdate(inData);
			}

			// apply the rotation delta too
			mpObjectNode->setRotationZ(-deltaAngle*mSpinSensitivity);

			// make sure the zoom is within limits
			if(mDistance>mMaxDistance)
			{			
				mDistance = mMaxDistance;
			}
			if(mDistance<mMinZoomDistance)
			{
				mDistance = mMinZoomDistance;
			}
		}		
	}

	/**
	* Triggered when there is a finger moves out of the screen or application window
	* \param inData holds the touch information
	*/
	void TutorApp6::onFingerExit(const TouchListener::TouchData& inData)
	{
		// just treat finger exit events as finger up events
		onFingerUp(inData);
	}

	/**
	* Render the Background Texture
	*/
	void TutorApp6::renderBackground()
	{
		const QVector2D windowSize(getWindow().getWindowAspect(), 1.f);		
		const QVector2D mPosition(0.f, 0.f);

		// Enables alpha blending
		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_TEXTURE_2D);

		// Binds the texture ID to OpenGL so it can be used
		glBindTexture(GL_TEXTURE_2D, mBackgroundTexture.getID()); 
		// Begin drawing GLQuads
		glBegin(GL_QUADS);
		glColor4f(1, 1, 1, 1);
		glTexCoord2f(0,1);
		glVertex2f(mPosition.x(), mPosition.y());
		glTexCoord2f(1,1);
		glVertex2f(mPosition.x()+windowSize.x(), mPosition.y());
		glTexCoord2f(1,0);
		glVertex2f(mPosition.x()+windowSize.x(), mPosition.y()+windowSize.y());
		glTexCoord2f(0,0);
		glVertex2f(mPosition.x(), mPosition.y()+windowSize.y());
		glEnd();
		glDisable(GL_TEXTURE_2D);
	}
}
